package cmd

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/spf13/cobra"
)

var particlesFlag bool
var formatFlag bool
var styleFlag int

// state
// 0 for nothing
// 1 for - (level 1)
// 2 for tab- (level 2)
// 3 for tabtab- (level 3)
var state = 0
var pre = false
var divh2, divh3 = false, false
var cnth2, cnth3 = 0, 0

// htmlCmd represents the html command
var htmlCmd = &cobra.Command{
	Use:   "html dir/base.md",
	Short: "md html is a tool to generate html file",
	Long: `"md html" is a tool to generate html file for your markdown file.
Input the full path of your markdown file,
	e.g.: $md html ../work/example.md
The tool will generate a example.html at the same directory.
`,
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) < 1 {
			log.Println("Error: args is not enough")
			forMoreHelp("html")
		}
		inputFilePath := args[0]
		if formatFlag {
			fmtFile(inputFilePath)
		}
		htmlFile(inputFilePath)
	},
}

func init() {
	rootCmd.AddCommand(htmlCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// htmlCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// htmlCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
	htmlCmd.Flags().BoolVarP(&formatFlag, "format", "f", true, "If you need to format the md file")
	htmlCmd.Flags().BoolVarP(&particlesFlag, "particles", "p", true, "If you need particles-js")
	htmlCmd.Flags().IntVarP(&styleFlag, "style", "s", 1, "the distance to style directory")
}

func htmlFile(inputFilePath string) {
	if !strings.HasSuffix(inputFilePath, ".md") {
		log.Printf("Error: your input filepath is: %s\n", inputFilePath)
		log.Println("Please input a filepath whose suffix is \".md\"")
		forMoreHelp("html")
	}
	// open input file dir/base.md
	iFile, err := os.Open(inputFilePath)
	if err != nil {
		log.Printf("Error when opening input file: %s\n", err)
		forMoreHelp("html")
	}
	defer iFile.Close()
	// create a html file
	size := len([]rune(inputFilePath))
	// dir/base.md -> dir/base
	removeMd := string([]rune(inputFilePath)[:size - 3])

	// dir/base -> base
	title := filepath.Base(removeMd)
	// dir/base -> dir/base.html
	htmlFilePath := removeMd + ".html"
	htmlFile, err := os.OpenFile(htmlFilePath, os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0644)
	if err != nil {
		log.Printf("Error when opening html file: %s\n", err)
	}
	defer htmlFile.Close()
	htmlWriter := bufio.NewWriter(htmlFile)

	styleStr := ""
	for i := 0; i < styleFlag; i++ {
		styleStr += "../"
	}
	// start of html file
	htmlWriter.WriteString("<!DOCTYPE html>\n")
	htmlWriter.WriteString("<html lang=\"zh-CN\">\n")
	htmlWriter.WriteString("\n")
	htmlWriter.WriteString("<head>\n")
	htmlWriter.WriteString("\t<meta charset=\"UTF-8\" />\n")
	htmlWriter.WriteString("\t<title>" + title + "</title>\n")
	// css file
	htmlWriter.WriteString("\t<link rel=\"stylesheet\" href=\"")
	htmlWriter.WriteString(styleStr)
	htmlWriter.WriteString("style/css/article.css\">\n")
	htmlWriter.WriteString("\t<!-- <script src=\"js/a.js\"></script> -->\n")
	htmlWriter.WriteString("</head>\n")
	htmlWriter.WriteString("\n")
	htmlWriter.WriteString("<body>\n")
	htmlWriter.WriteString("<a id=\"top\" href=\"#\"></a>\n")
	if particlesFlag {
		htmlWriter.WriteString("<div id=\"particles-js\"></div>\n")
	}

	scanner := bufio.NewScanner(iFile)
	for scanner.Scan() {
		//fmt.Println(scanner.Text())
		newStr := mdToHtml(scanner.Text())
		//fmt.Println(newStr)
		htmlWriter.WriteString(newStr)
	}
	if err := scanner.Err(); err != nil {
		log.Fatalln(err)
	}
	// end of html file
	if state == 2 {
		htmlWriter.WriteString("</ul>\n")
		htmlWriter.WriteString("</ul>\n")
		state = 0
	} else if state == 1 {
		htmlWriter.WriteString("\t\t</ul>\n")
		state = 0
	}
	if divh3 {
		htmlWriter.WriteString("</div>")
		divh3 = false
	}
	if divh2 {
		htmlWriter.WriteString("</div>")
		divh2 = false
	}
	htmlWriter.WriteString("\t</div>\n")

	// script
	if particlesFlag {
		htmlWriter.WriteString("<script src=\"")
		htmlWriter.WriteString(styleStr)
		htmlWriter.WriteString("style/js/particles.min.js\"></script>\n")
		htmlWriter.WriteString("<script src=\"")
		htmlWriter.WriteString(styleStr)
		htmlWriter.WriteString("style/js/particles.config.js\"></script>\n")
	}
	htmlWriter.WriteString("</body>\n")
	htmlWriter.WriteString("</html>\n")

	htmlWriter.Flush()
}


func mdToHtml(mdStr string) (htmlStr string) {
	if len(mdStr) == 0 {
		return
	}
	index := 0
	toState0 := func() {
		if state == 3 {
			htmlStr += "</ul>\n"
			htmlStr += "</ul>\n"
			htmlStr += "</ul>\n"
		} else if state == 2 {
			htmlStr += "</ul>\n"
			htmlStr += "</ul>\n"
		} else if state == 1 {
			htmlStr += "</ul>\n"
		}
		state = 0
	}
	toState1 := func() {
		if state == 3 {
			htmlStr += "</ul>\n"
			htmlStr += "</ul>\n"
		} else if state == 2 {
			htmlStr += "</ul>\n"
		} else if state == 0 {
			htmlStr += "<ul>\n"
		}
		state = 1
	}
	toState2 := func() {
		if state == 3 {
			htmlStr += "</ul>\n"
		} else if state == 1 {
			htmlStr += "<ul>\n"
		}
		state = 2
	}
	closeDivh3 := func() {
		if divh3 {
			htmlStr += "</div>"
			divh3 = false
		}
	}
	closeDivh2 := func() {
		if divh2 {
			htmlStr += "</div>"
			divh2 = false
		}
	}
	if pre {
		if strings.HasPrefix(mdStr, "```") {
			htmlStr += "</pre>\n"
			pre = false
		} else {
			htmlStr += mdStr + "\n"
		}
	} else if strings.HasPrefix(mdStr, "```") {
		toState0()
		pre = true
		htmlStr += "<pre>\n"
	} else if strings.HasPrefix(mdStr, "# ") {
		// # -> h1
		toState0()
		closeDivh3()
		closeDivh2()
		if strings.HasPrefix(mdStr, "# Meta") {
			// # Meta
			htmlStr += "<div class=\"meta\">\n"
			htmlStr += "<h1>Meta</h1>\n"
		} else if strings.HasPrefix(mdStr, "# Text") {
			// # Text, add the /div of meta
			htmlStr += "</div>\n"
			htmlStr += "<div class=\"text\">\n"
			htmlStr += "<h1>Text</h1>\n"
		} else {
			fmt.Println("other h1")
		}
	} else if strings.HasPrefix(mdStr, "## ") {
		// ## -> h2
		cnth2++
		toState0()
		closeDivh3()
		closeDivh2()
		htmlStr += "<div class=\"h2i" + strconv.Itoa(cnth2) + "\">\n"
		divh2 = true
		index = 3
		htmlStr += "<h2>" + string([]rune(mdStr)[index:]) + "</h2>\n"
	} else if strings.HasPrefix(mdStr, "### ") {
		// ### -> h3
		cnth3++
		toState0()
		closeDivh3()
		htmlStr += "<div class=\"h3i" + strconv.Itoa(cnth3) + "\">\n"
		divh3 = true
		index = 4
		htmlStr += "<h3>" + string([]rune(mdStr)[index:]) + "</h3>\n"
	} else if strings.HasPrefix(mdStr, "- ") {
		// - -> ul + li
		toState1()

		index = 2
		htmlStr += "<li>" + string([]rune(mdStr)[index:]) + "</li>\n"
	} else if strings.HasPrefix(mdStr, "    - ") {
		// tab- -> ul + li (level 2)
		toState2()
		index = 6
		htmlStr += "<li>" + string([]rune(mdStr)[index:]) + "</li>\n"
	} else if strings.HasPrefix(mdStr, "        - ") {
		// tabtab- -> ul + li (level 3)
		if state == 2 {
			htmlStr += "<ul>\n"
			state = 3
		}
		index = 10
		htmlStr += "<li>" + string([]rune(mdStr)[index:]) + "</li>\n"
	} else if strings.HasPrefix(mdStr, "![") {
		toState0()
		htmlStr += "<div class=\"img\">\n"
		index = strings.LastIndex(mdStr, "(")
		index++
		htmlStr += "<img src=\"" + string([]rune(mdStr)[index:len(mdStr)-1]) + "\" />\n"
		htmlStr += "</div>"
	} else if strings.HasPrefix(mdStr, "---") {
		toState0()
		htmlStr += "<hr/>\n"
	} else {
		fmt.Println("bug: " + mdStr)
	}
	// 处理 [text](href) 格式的超链接
	for strings.Contains(htmlStr, "](") {
		tempStr := ""
		id1, id3 := 0, 0
		id2 := strings.Index(htmlStr, "](")
		for i := id2; i >= 0; i-- {
			if htmlStr[i] == '[' {
				id1 = i
				break
			}
		}
		for i := id2; i < len(htmlStr); i++ {
			if htmlStr[i] == ')' {
				id3 = i
				break
			}
		}
		tempStr += string([]byte(htmlStr)[:id1])
		tempStr += "<a href=\"" + string([]byte(htmlStr)[id2+2:id3]) + "\">"
		tempStr += string([]byte(htmlStr)[id1+1:id2]) + "</a>"
		tempStr += string([]byte(htmlStr)[id3+1:])
		htmlStr = tempStr
	}
	return
}